/**
 * @file basic_metrics.h
 *
 * Computation of basic metrics
 *
 * @author Dahua Lin
 */

#ifdef _MSC_VER
#pragma once
#endif

#ifndef GMLPP_BASIC_METRICS_H_
#define GMLPP_BASIC_METRICS_H_

#include <gmlpp/base/arma.h>
#include <gmlpp/core/pdmat.h>

namespace gml
{
	/********************************************
	 *
	 *  Calculation functions
	 *
	 ********************************************/

	// sqL2

	template<typename T>
	inline T sqL2_dist(uword n, const T *x, const T *y)
	{
		T s = 0;
		for (uword i = 0; i < n; ++i) s += math::sqr(x[i] - y[i]);
		return s;
	}

	template<typename T>
	inline T sqL2_dist(const Mat<T>& x, const Mat<T>& y)
	{
		check_arg( is_same_size(x, y) );
		return sqL2_dist(x.n_elem, x.memptr(), y.memptr());
	}

	template<typename T>
	void colwise_sqL2_dist(const Mat<T>& X, const Mat<T>& Y, const Mat<T>& r)
	{
		check_arg( is_same_size(X, Y) );

		uword m = X.n_rows;
		uword n = X.n_cols;
		r.set_size(1, n);

		for (uword i = 0; i < n; ++i)
		{
			r[i] = sqL2_dist(m, X.colptr(i), Y.colptr(i));
		}
	}

	template<typename T>
	inline Row<T> colwise_sqL2_dist(const Mat<T>& X, const Mat<T>& Y)
	{
		Row<T> r;
		colwise_sqL2_dist(X, Y, r);
		return r;
	}

	template<typename T>
	void pairwise_sqL2_dist(const Mat<T>& X, const Mat<T>& Y, const Mat<T>& R)
	{
		uword m = X.n_cols;
		uword n = Y.n_cols;

		R = X.t() * Y;

		Col<T> sx2 = arma::sum(arma::square(X), 0).t();
		Row<T> sy2 = arma::sum(arma::square(Y), 0);

		for (uword j = 0; j < n; ++j)
		{
			for (uword i = 0; i < m; ++i)
			{
				T& v = R.at(i, j);
				v = (sx2[i] + sy2[j] - T(2) * v);
				if (v < 0) v = 0;
			}
		}
	}

	template<typename T>
	inline Mat<T> pairwise_sqL2_dist(const Mat<T>& X, const Mat<T>& Y)
	{
		Mat<T> R;
		pairwise_sqL2_dist(X, Y, R);
		return R;
	}


	/********************************************
	 *
	 *  Metric Object
	 *
	 ********************************************/


	template<class Derived, typename T>
	class MetricBase
	{
	public:
		GML_ENSURE_INLINE const Derived& derived() const
		{
			return static_cast<const Derived&>(*this);
		}

		GML_ENSURE_INLINE T operator() (const Col<T>& x, const Col<T>& y) const
		{
			return derived().operator() (x, y);
		}

		GML_ENSURE_INLINE void colwise_eval(const Mat<T>& X, const Mat<T>& Y, Mat<T>& r) const
		{
			derived().colwise_eval(X, Y, r);
		}

		GML_ENSURE_INLINE Row<T> colwise_eval(const Mat<T>& X, const Mat<T>& Y) const
		{
			return derived().colwise_eval(X, Y);
		}

		GML_ENSURE_INLINE void pairwise_eval(const Mat<T>& X, const Mat<T>& Y, Mat<T>& R) const
		{
			derived().pairwise_eval(X, Y, R);
		}

		GML_ENSURE_INLINE Mat<T> pairwise_eval(const Mat<T>& X, const Mat<T>& Y) const
		{
			return derived().pairwise_eval(X, Y);
		}
	};

	template<typename T>
	class SqL2Dist : public MetricBase<SqL2Dist<T>, T>
	{
	public:
		GML_ENSURE_INLINE T operator() (const Col<T>& x, const Col<T>& y) const
		{
			return sqL2_dist(x, y);
		}

		GML_ENSURE_INLINE void colwise_eval(const Mat<T>& X, const Mat<T>& Y, Mat<T>& r) const
		{
			colwise_sqL2_dist(X, Y, r);
		}

		GML_ENSURE_INLINE Row<T> colwise_eval(const Mat<T>& X, const Mat<T>& Y) const
		{
			return colwise_sqL2_dist(X, Y);
		}

		GML_ENSURE_INLINE void pairwise_eval(const Mat<T>& X, const Mat<T>& Y, Mat<T>& R) const
		{
			pairwise_sqL2_dist(X, Y, R);
		}

		GML_ENSURE_INLINE Mat<T> pairwise_eval(const Mat<T>& X, const Mat<T>& Y) const
		{
			return pairwise_sqL2_dist(X, Y);
		}

	};







}

#endif /* BASIC_METRICS_H_ */
